Un guide complet sur les Server Actions de Next.js 14, couvrant les bonnes pratiques de gestion des formulaires, la validation des données, la sécurité et les techniques avancées pour créer des applications web modernes.
Server Actions Next.js 14 : Maîtriser les bonnes pratiques de gestion des formulaires
Next.js 14 introduit des fonctionnalités puissantes pour créer des applications web performantes et conviviales. Parmi celles-ci, les Server Actions se distinguent comme une manière transformatrice de gérer les soumissions de formulaires et les mutations de données directement sur le serveur. Ce guide offre un aperçu complet des Server Actions dans Next.js 14, en se concentrant sur les bonnes pratiques pour la gestion des formulaires, la validation des données, la sécurité et les techniques avancées. Nous explorerons des exemples pratiques et fournirons des conseils concrets pour vous aider à construire des applications web robustes et évolutives.
Que sont les Server Actions de Next.js ?
Les Server Actions sont des fonctions asynchrones qui s'exécutent sur le serveur et peuvent être appelées directement depuis des composants React. Elles éliminent le besoin de routes d'API traditionnelles pour gérer les soumissions de formulaires et les mutations de données, ce qui se traduit par un code simplifié, une sécurité améliorée et des performances accrues. Les Server Actions sont des React Server Components (RSC), ce qui signifie qu'elles sont exécutées sur le serveur, entraînant des chargements de page initiaux plus rapides et un meilleur SEO.
Principaux avantages des Server Actions :
- Code simplifié : Réduisez le code répétitif en éliminant le besoin de routes d'API distinctes.
- Sécurité améliorée : L'exécution côté serveur minimise les vulnérabilités côté client.
- Performances accrues : Exécutez les mutations de données directement sur le serveur pour des temps de réponse plus rapides.
- SEO optimisé : Tirez parti du rendu côté serveur pour une meilleure indexation par les moteurs de recherche.
- Sûreté des types : Bénéficiez d'une sûreté des types de bout en bout avec TypeScript.
Configurer votre projet Next.js 14
Avant de plonger dans les Server Actions, assurez-vous d'avoir un projet Next.js 14 configuré. Si vous partez de zéro, créez un nouveau projet avec la commande suivante :
npx create-next-app@latest my-next-app
Assurez-vous que votre projet utilise la structure de répertoires app
pour profiter pleinement des Server Components et des Actions.
Gestion de formulaire de base avec les Server Actions
Commençons par un exemple simple : un formulaire qui soumet des données pour créer un nouvel élément dans une base de données. Nous utiliserons un formulaire simple avec un champ de saisie et un bouton de soumission.
Exemple : Créer un nouvel élément
D'abord, définissez une fonction Server Action au sein de votre composant React. Cette fonction gérera la logique de soumission du formulaire sur le serveur.
// app/components/CreateItemForm.tsx
'use client';
import { useState } from 'react';
async function createItem(formData: FormData) {
'use server'
const name = formData.get('name') as string;
// Simuler une interaction avec la base de données
console.log('Création de l'élément :', name);
await new Promise((resolve) => setTimeout(resolve, 1000)); // Simuler la latence
console.log('Élément créé avec succès !');
}
export default function CreateItemForm() {
const [isSubmitting, setIsSubmitting] = useState(false);
async function handleSubmit(formData: FormData) {
setIsSubmitting(true);
await createItem(formData);
setIsSubmitting(false);
}
return (
);
}
Explication :
- La directive
'use client'
indique qu'il s'agit d'un composant client. - La fonction
createItem
est marquée avec la directive'use server'
, indiquant qu'il s'agit d'une Server Action. - La fonction
handleSubmit
est une fonction côté client qui appelle la Server Action. Elle gère également l'état de l'interface utilisateur, comme la désactivation du bouton lors de la soumission. - La propriété
action
de l'élément<form>
est définie sur la fonctionhandleSubmit
. - La méthode
formData.get('name')
récupère la valeur du champ de saisie 'name'. - Le
await new Promise
simule une opération de base de données et ajoute de la latence.
Validation des données
La validation des données est cruciale pour garantir leur intégrité et prévenir les vulnérabilités de sécurité. Les Server Actions offrent une excellente opportunité d'effectuer une validation côté serveur. Cette approche aide à atténuer les risques associés à la seule validation côté client.
Exemple : Valider les données d'entrée
Modifiez la Server Action createItem
pour y inclure une logique de validation.
// app/components/CreateItemForm.tsx
'use client';
import { useState } from 'react';
async function createItem(formData: FormData) {
'use server'
const name = formData.get('name') as string;
if (!name || name.length < 3) {
throw new Error('Le nom de l\'élément doit comporter au moins 3 caractères.');
}
// Simuler une interaction avec la base de données
console.log('Création de l\'élément :', name);
await new Promise((resolve) => setTimeout(resolve, 1000)); // Simuler la latence
console.log('Élément créé avec succès !');
}
export default function CreateItemForm() {
const [isSubmitting, setIsSubmitting] = useState(false);
const [errorMessage, setErrorMessage] = useState(null);
async function handleSubmit(formData: FormData) {
setIsSubmitting(true);
setErrorMessage(null);
try {
await createItem(formData);
} catch (error: any) {
setErrorMessage(error.message || 'Une erreur est survenue.');
} finally {
setIsSubmitting(false);
}
}
return (
{errorMessage && {errorMessage}
}
);
}
Explication :
- La fonction
createItem
vérifie maintenant si lename
est valide (au moins 3 caractères de long). - Si la validation échoue, une erreur est levée.
- La fonction
handleSubmit
est mise à jour pour intercepter les erreurs levées par la Server Action et afficher un message d'erreur à l'utilisateur.
Utiliser des bibliothèques de validation
Pour des scénarios de validation plus complexes, envisagez d'utiliser des bibliothèques de validation comme :
- Zod : Une bibliothèque de déclaration et de validation de schémas axée sur TypeScript.
- Yup : Un constructeur de schémas JavaScript pour l'analyse, la validation et la transformation de valeurs.
Voici un exemple avec Zod :
// app/utils/validation.ts
import { z } from 'zod';
export const CreateItemSchema = z.object({
name: z.string().min(3, 'Le nom de l\'élément doit comporter au moins 3 caractères.'),
});
// app/components/CreateItemForm.tsx
'use client';
import { useState } from 'react';
import { CreateItemSchema } from '../utils/validation';
async function createItem(formData: FormData) {
'use server'
const name = formData.get('name') as string;
const validatedFields = CreateItemSchema.safeParse({ name });
if (!validatedFields.success) {
return { errors: validatedFields.error.flatten().fieldErrors };
}
// Simuler une interaction avec la base de données
console.log('Création de l\'élément :', name);
await new Promise((resolve) => setTimeout(resolve, 1000)); // Simuler la latence
console.log('Élément créé avec succès !');
}
export default function CreateItemForm() {
const [isSubmitting, setIsSubmitting] = useState(false);
const [errorMessage, setErrorMessage] = useState(null);
async function handleSubmit(formData: FormData) {
setIsSubmitting(true);
setErrorMessage(null);
try {
await createItem(formData);
} catch (error: any) {
setErrorMessage(error.message || 'Une erreur est survenue.');
} finally {
setIsSubmitting(false);
}
}
return (
{errorMessage && {errorMessage}
}
);
}
Explication :
- Le
CreateItemSchema
définit les règles de validation pour le champname
en utilisant Zod. - La méthode
safeParse
tente de valider les données d'entrée. Si la validation échoue, elle renvoie un objet avec les erreurs. - L'objet
errors
contient des informations détaillées sur les échecs de validation.
Considérations de sécurité
Les Server Actions améliorent la sécurité en exécutant le code sur le serveur, mais il est toujours crucial de suivre les bonnes pratiques de sécurité pour protéger votre application contre les menaces courantes.
Prévenir les attaques de type Cross-Site Request Forgery (CSRF)
Les attaques CSRF exploitent la confiance qu'un site web accorde au navigateur d'un utilisateur. Pour prévenir les attaques CSRF, mettez en œuvre des mécanismes de protection CSRF.
Next.js gère automatiquement la protection CSRF lors de l'utilisation des Server Actions. Le framework génère et valide un jeton CSRF pour chaque soumission de formulaire, garantissant que la requête provient bien de votre application.
Gérer l'authentification et l'autorisation des utilisateurs
Assurez-vous que seuls les utilisateurs autorisés peuvent effectuer certaines actions. Mettez en œuvre des mécanismes d'authentification et d'autorisation pour protéger les données et fonctionnalités sensibles.
Voici un exemple utilisant NextAuth.js pour protéger une Server Action :
// app/components/CreateItemForm.tsx
'use client';
import { useState } from 'react';
import { getServerSession } from 'next-auth';
import { authOptions } from '../../app/api/auth/[...nextauth]/route';
async function createItem(formData: FormData) {
'use server'
const session = await getServerSession(authOptions);
if (!session) {
throw new Error('Non autorisé');
}
const name = formData.get('name') as string;
// Simuler une interaction avec la base de données
console.log('Création de l\'élément :', name, 'par l\'utilisateur :', session.user?.email);
await new Promise((resolve) => setTimeout(resolve, 1000)); // Simuler la latence
console.log('Élément créé avec succès !');
}
export default function CreateItemForm() {
const [isSubmitting, setIsSubmitting] = useState(false);
const [errorMessage, setErrorMessage] = useState(null);
async function handleSubmit(formData: FormData) {
setIsSubmitting(true);
setErrorMessage(null);
try {
await createItem(formData);
} catch (error: any) {
setErrorMessage(error.message || 'Une erreur est survenue.');
} finally {
setIsSubmitting(false);
}
}
return (
{errorMessage && {errorMessage}
}
);
}
Explication :
- La fonction
getServerSession
récupère les informations de session de l'utilisateur. - Si l'utilisateur n'est pas authentifié (pas de session), une erreur est levée, empêchant l'exécution de la Server Action.
Nettoyer les données d'entrée
Nettoyez les données d'entrée pour prévenir les attaques de type Cross-Site Scripting (XSS). Les attaques XSS se produisent lorsque du code malveillant est injecté dans un site web, compromettant potentiellement les données des utilisateurs ou les fonctionnalités de l'application.
Utilisez des bibliothèques comme DOMPurify
ou sanitize-html
pour nettoyer les entrées fournies par l'utilisateur avant de les traiter dans vos Server Actions.
Techniques avancées
Maintenant que nous avons couvert les bases, explorons quelques techniques avancées pour utiliser efficacement les Server Actions.
Mises à jour optimistes
Les mises à jour optimistes offrent une meilleure expérience utilisateur en mettant immédiatement à jour l'interface utilisateur comme si l'action allait réussir, avant même que le serveur ne le confirme. Si l'action échoue sur le serveur, l'interface utilisateur est restaurée à son état précédent.
// app/components/UpdateItemForm.tsx
'use client';
import { useState } from 'react';
async function updateItem(id: string, formData: FormData) {
'use server'
const name = formData.get('name') as string;
// Simuler une interaction avec la base de données
console.log('Mise à jour de l\'élément :', id, 'avec le nom :', name);
await new Promise((resolve) => setTimeout(resolve, 1000)); // Simuler la latence
// Simuler un échec (à des fins de démonstration)
const shouldFail = Math.random() < 0.5;
if (shouldFail) {
throw new Error('Échec de la mise à jour de l\'élément.');
}
console.log('Élément mis à jour avec succès !');
return { name }; // Renvoyer le nom mis à jour
}
export default function UpdateItemForm({ id, initialName }: { id: string; initialName: string }) {
const [isSubmitting, setIsSubmitting] = useState(false);
const [errorMessage, setErrorMessage] = useState(null);
const [itemName, setItemName] = useState(initialName);
async function handleSubmit(formData: FormData) {
setIsSubmitting(true);
setErrorMessage(null);
// Mettre à jour l'UI de manière optimiste
const newName = formData.get('name') as string;
setItemName(newName);
try {
const result = await updateItem(id, formData);
// En cas de succès, la mise à jour est déjà reflétée dans l'UI via setItemName
} catch (error: any) {
setErrorMessage(error.message || 'Une erreur est survenue.');
// Rétablir l'UI en cas d'erreur
setItemName(initialName);
} finally {
setIsSubmitting(false);
}
}
return (
Nom actuel : {itemName}
{errorMessage && {errorMessage}
}
);
}
Explication :
- Avant d'appeler la Server Action, l'interface utilisateur est immédiatement mise à jour avec le nouveau nom de l'élément en utilisant
setItemName
. - Si la Server Action échoue, l'interface utilisateur est restaurée au nom original de l'élément.
Revalidation des données
Après qu'une Server Action a modifié des données, vous devrez peut-être revalider les données mises en cache pour vous assurer que l'interface utilisateur reflète les derniers changements. Next.js offre plusieurs moyens de revalider les données :
- Revalidate Path : Revalider le cache pour un chemin spécifique.
- Revalidate Tag : Revalider le cache pour les données associées à une balise spécifique.
Voici un exemple de revalidation d'un chemin après la création d'un nouvel élément :
// app/components/CreateItemForm.tsx
'use client';
import { useState } from 'react';
import { revalidatePath } from 'next/cache';
async function createItem(formData: FormData) {
'use server'
const name = formData.get('name') as string;
// Simuler une interaction avec la base de données
console.log('Création de l\'élément :', name);
await new Promise((resolve) => setTimeout(resolve, 1000)); // Simuler la latence
console.log('Élément créé avec succès !');
revalidatePath('/items'); // Revalider le chemin /items
}
export default function CreateItemForm() {
const [isSubmitting, setIsSubmitting] = useState(false);
const [errorMessage, setErrorMessage] = useState(null);
async function handleSubmit(formData: FormData) {
setIsSubmitting(true);
setErrorMessage(null);
try {
await createItem(formData);
} catch (error: any) {
setErrorMessage(error.message || 'Une erreur est survenue.');
} finally {
setIsSubmitting(false);
}
}
return (
{errorMessage && {errorMessage}
}
);
}
Explication :
- La fonction
revalidatePath('/items')
invalide le cache pour le chemin/items
, garantissant que la prochaine requête vers ce chemin récupérera les données les plus récentes.
Bonnes pratiques pour les Server Actions
Pour maximiser les avantages des Server Actions, tenez compte des bonnes pratiques suivantes :
- Gardez les Server Actions petites et ciblées : Les Server Actions doivent effectuer une tâche unique et bien définie. Évitez la logique complexe au sein des Server Actions pour maintenir la lisibilité et la testabilité.
- Utilisez des noms descriptifs : Donnez à vos Server Actions des noms descriptifs qui indiquent clairement leur objectif.
- Gérez les erreurs avec élégance : Mettez en œuvre une gestion robuste des erreurs pour fournir des retours informatifs à l'utilisateur et éviter les plantages de l'application.
- Validez les données de manière approfondie : Effectuez une validation complète des données pour garantir leur intégrité et prévenir les vulnérabilités de sécurité.
- Sécurisez vos Server Actions : Mettez en œuvre des mécanismes d'authentification et d'autorisation pour protéger les données et fonctionnalités sensibles.
- Optimisez les performances : Surveillez les performances de vos Server Actions et optimisez-les si nécessaire pour garantir des temps de réponse rapides.
- Utilisez la mise en cache efficacement : Tirez parti des mécanismes de mise en cache de Next.js pour améliorer les performances et réduire la charge de la base de données.
Pièges courants et comment les éviter
Bien que les Server Actions offrent de nombreux avantages, il existe certains pièges courants à connaître :
- Server Actions trop complexes : Évitez de mettre trop de logique dans une seule Server Action. Décomposez les tâches complexes en fonctions plus petites et plus faciles à gérer.
- Négliger la gestion des erreurs : Incluez toujours une gestion des erreurs pour intercepter les erreurs inattendues et fournir des retours utiles à l'utilisateur.
- Ignorer les bonnes pratiques de sécurité : Suivez les bonnes pratiques de sécurité pour protéger votre application contre les menaces courantes comme XSS et CSRF.
- Oublier de revalider les données : Assurez-vous de revalider les données mises en cache après qu'une Server Action a modifié des données pour maintenir l'interface utilisateur à jour.
Conclusion
Les Server Actions de Next.js 14 offrent un moyen puissant et efficace de gérer les soumissions de formulaires et les mutations de données directement sur le serveur. En suivant les bonnes pratiques décrites dans ce guide, vous pouvez créer des applications web robustes, sécurisées et performantes. Adoptez les Server Actions pour simplifier votre code, renforcer la sécurité et améliorer l'expérience utilisateur globale. En intégrant ces principes, tenez compte de l'impact mondial de vos choix de développement. Assurez-vous que vos formulaires et vos processus de gestion des données sont accessibles, sécurisés et conviviaux pour des publics internationaux variés. Cet engagement en faveur de l'inclusivité améliorera non seulement l'utilisabilité de votre application, mais élargira également sa portée et son efficacité à l'échelle mondiale.